CloudFormation カスタムリソースで Amazon Connect インスタンス・ユーザ作成をやってみた
こんにちは、森田です。
Amazon Connect では、プレビュー版APIがあるものの、CloudFormationでは提供されていません。
そこで、CloudFormation のカスタムリソースを用いてAmazon Connect インスタンス・ユーザ作成をやってみました。
CloudFormationテンプレート
では、早速ですが、以下が CloudFormationテンプレートとなります。
AWSTemplateFormatVersion: "2010-09-09" Parameters: InstanceAlias: Type: String Description: New Instance Alias UserName: Type: String Default: admin Description: New User Name Password: Type: String Default: admin Description: New User Password NoEcho: true FirstName: Type: String Description: New User first name LastName: Type: String Description: New User last name Mail: Type: String Description: New User mail Resources: UserPasswordParameter: Type: AWS::SSM::Parameter Properties: Name: !Sub 'ConnectMasterPassword-${AWS::StackName}' Type: String Value: !Ref Password Description: "MasterUserPassword for Connect" ConnectInstanceHandler: Type: Custom::ConnectInstanceHandler Properties: ServiceToken: !GetAtt "LambdaFunction.Arn" LambdaFunction: Type: AWS::Lambda::Function Properties: FunctionName : !Join ['-', [!Sub '${AWS::StackName}-Lambda', !Select [0, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId' ]]]]]] Role: !GetAtt "LambdaExecutionRole.Arn" Runtime: "python3.8" Handler: index.lambda_handler Timeout: "300" Environment: Variables: InstanceAlias : !Ref InstanceAlias FirstName : !Ref FirstName LastName : !Ref LastName Mail : !Ref Mail UserName : !Ref UserName Password : !Ref UserPasswordParameter Code: ZipFile: | import cfnresponse import sys import os import boto3 import time def lambda_handler(event, context): if 'RequestType' not in event: return "Overwrite" if event['RequestType'] == 'Create': create_connect(context) cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'}) if event['RequestType'] == 'Delete': print('Delete') delete_connect() cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'}) if event['RequestType'] == 'Update': print('Update') cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'}) def get_ssm(key): ssm = boto3.client('ssm', "ap-northeast-1") response = ssm.get_parameter( Name=key, WithDecryption=True, ) try: value = response['Parameter']['Value'] except: value = None return value def create_connect(context): client = boto3.client('connect') instance_alias = os.getenv('InstanceAlias') user_name = os.getenv('UserName') passwd = get_ssm(os.getenv('Password')) f_n = os.getenv('FirstName') l_n = os.getenv('LastName') mail= os.getenv('Mail') res = client.create_instance( IdentityManagementType='CONNECT_MANAGED', InstanceAlias=instance_alias, InboundCallsEnabled=True, OutboundCallsEnabled=True ) assert res['ResponseMetadata']['HTTPStatusCode'] == 200, 'Connect インスタンスが正常に作成できませんでした' InstanceId = res["Id"] # Instance ID Save update_environ( context.invoked_function_arn, InstanceId ) # インスタンスがACTIVEになるまで待つ status = '' while status != 'ACTIVE': time.sleep(5) res= client.describe_instance( InstanceId=InstanceId ) status = res['Instance'] ['InstanceStatus'] # RoutingProfileIdを取得する res = client.list_routing_profiles( InstanceId=InstanceId ) RoutingProfileId = res['RoutingProfileSummaryList'][0]['Id'] # SecurityProfileIdsを取得する res = client.list_security_profiles( InstanceId=InstanceId ) SecurityProfileAdminId = list(filter(lambda x: x['Name']=='Admin', res['SecurityProfileSummaryList']))[0]['Id'] res = client.create_user( Username=user_name, Password=passwd, IdentityInfo={ 'FirstName': f_n, 'LastName': l_n, 'Email': mail }, PhoneConfig={ 'PhoneType': 'SOFT_PHONE' }, SecurityProfileIds=[ SecurityProfileAdminId, ], RoutingProfileId=RoutingProfileId, InstanceId=InstanceId ) assert res['ResponseMetadata']['HTTPStatusCode'] == 200, 'Connect ユーザが正常に作成できませんでした' print('Amazon Connect Setup Done!!') print('https://{}.my.connect.aws'.format(instance_alias)) def delete_connect(): client = boto3.client('connect') InstanceId = os.getenv('InstanceId') response = client.delete_instance( InstanceId=InstanceId ) def update_environ(function_arn, InstanceId): client = boto3.client('lambda') response = client.update_function_configuration( FunctionName=function_arn, Environment={ 'Variables': { 'InstanceId': InstanceId } } ) LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: sample-lambda-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - ssm:GetParameter Resource: "*" - Effect: Allow Action: - lambda:UpdateFunctionConfiguration Resource: "*" ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonConnect_FullAccess
作成時と削除時のアクション
作成時
スタック作成時には、create_connect関数を実行します。こちらのインスタンス・ユーザ作成のコードについては、以下をご参照ください。
上記の記事との変更点は、パスワードをそのまま環境変数で渡さず、Parameter Storeで渡すように変更しています。
削除時
削除時は、delete_connect関数を実行します。Lambdaの環境変数より Connect インスタンスID を読み込み、Boto3で Connect インスタンス の削除を行っております。
試してみる
スタックの作成
上記のテンプレートを実際に流してみます。
パラメータでは、ユーザ名、パスワード等が求められますので、入力します。
実行後、しばらく待つと以下のようにコネクトインスタンスが確認できます。
スタックの削除
スタックの削除も行ってみます。スタックの削除後、コネクトインスタンスも削除されていることが確認できます。
最後に
以前作成したPythonスクリプトをCloudFormationとしてカプセル化することで、より扱いやすくなりました。
ただ、他のCloudFormationとして動作させるための構文は追加しているので、コードが長くなっています。
このまま CloudFormationのテンプレート を再利用するには、大変ですので、近いうちにモジュールとしてCloudFormationレジストリ登録もやってみたいと思います。